package edu.vt.ece.rt.dstm;

import java.util.concurrent.atomic.AtomicInteger;

import edu.vt.ece.rt.dstm.contention.ContentionManager;
import edu.vt.ece.rt.quorum.Cluster;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.quorum.QuroumFailureException;
import edu.vt.ece.rt.quorum.quorum.ReadQuorum;
import edu.vt.ece.rt.quorum.quorum.WriteQuorum;
import edu.vt.ece.rt.quorum.util.Names;
import edu.vt.ece.rt.util.Logger.Logger;


/**
 * Application Interface to DSTM
 * @author Sudhanshu
 */
public class TMProxy{
	ReadQuorum rQ;
	WriteQuorum wQ;
	AtomicInteger requestReconfig; 
	
	static TMProxy tp;
	
	public static TMProxy getTMProxy(){
		if(tp == null){
			tp = new TMProxy();
			tp.config();
		}
		return tp;
	}
	
	public static void start(){
		if(tp == null){
			tp = new TMProxy();
			tp.config();
		}
	}
	/*
	 * Initial TMProxy Should be called after finding the rQ & wQ for node
	 */
	public void config(){
		Logger.debug("Initiating the TMProxy");
		if(Boolean.getBoolean(Names.reap)){
			ClustersManager.calculateDistance();			
		}
		getTMProxy().wQ = WriteQuorum.getQuorum();
		getTMProxy().rQ = ReadQuorum.getQuorum();
		requestReconfig = new AtomicInteger(0);
		Logger.debug("TM Initiation Completed");
	}
	
	/**
	 * Returns the Object copy if exist in quorum, else returns null
	 */
	public final QObject read(String Id,Long tnx){
		ContentionManager.open(tnx);
		return reader(Id,tnx);
	}
	
	public final QObject reader(String Id, Long tnx) {
		QObject ob = null;
		try {
			ob = rQ.read(Id, tnx, true);
		} catch (QuroumFailureException e) {
			reconfigCluster();
			return reader(Id, tnx);		//Make Network failure  invisible to application
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ob;
	}
	
	/**
	 * To re-create the rQ, wQ
	 */
	private void reconfigCluster() {
		if(requestReconfig.incrementAndGet() == 1)  // if you are first, reconfigure cluster
			this.config();		//Initiate again to get new clusters
		while(requestReconfig.get() !=0);			//wait until reconfiguration done		
	}

	/**
	 * Returns the Object copy if exist in quorum, else returns null
	 */
	public QObject write(String Id, Long tnx){
		ContentionManager.open(tnx);
		return writer(Id, tnx);
	}
	
	public QObject writer(String Id, Long tnx){
		QObject ob = null;
		try {
			ob = rQ.read(Id, tnx, false);
//			ob = wQ.write(Id, tnx, false);
		} catch (QuroumFailureException e) {
			reconfigCluster();
			return writer(Id, tnx);		//Make Network failure  invisible to application
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ob;
	}
	
	public boolean requestCommit(Long tnx){
		Boolean b = null;			
		try {
			b = wQ.requestCommit(tnx);
		} catch (QuroumFailureException e) {
			reconfigCluster();
			return false;			//fail the transaction, TODO: Might be retried to request commit
		} catch (Exception e) {
			e.printStackTrace();
		}
		return b;
	}
	
	public Cluster getReadChannel(){
		return rQ.getReaders();
	}
	
	public Cluster getWriteChannel(){
		return wQ.getWriters();
	}

	/**Publishes the object to the quorum, depending on 
	 * Initialized or uninitialized objects
	 * @param Object
	 */
	public void publishObject(QObject obj) {
		wQ.addToQuorum(obj);		
	}
	
}
